// Copyright (C) 2008 Taylor L. Riche <riche@cs.utexas.edu>
//  
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software 
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

/**
 * 
 */
package core;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Hashtable;
import syscomp.entities.*;
import appcomp.entities.*;

/**
 * @author riche
 *
 */
public class CodeGenerator {
	
	private Hashtable<String, Entity> entityDictionary = new Hashtable<String, Entity>(); 
	
	public void generateProfiler(String appName, String sysName, ArrayList<Operator> opList) 
	{
		String filename = "lc_prof_main.cc";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}
		
		file.println("#include <iostream>");
		file.println("#include <fstream>");
		file.println("#include \"lc_" + appName + "_Application.h\"");
		file.println("#include <sysmodel/lc_" + sysName + "_System.h>");
		file.println();
		file.println("int");
		file.println("main(int argc, int ** argv)");
		file.println("{");
		file.println();

		file.println("\t" + appName + " myApp;");
		file.println("\t" + sysName + " mySys;");
		file.println();

		// setup system and app
		file.println("\tmyApp.setupProfiler(&mySys);");
		
		// open xml file
		file.println("\tstd::ofstream outfile;");
		file.println("\toutfile.open(\"" + appName + "_profData.xml\", std::ios::out);");
		file.println("\toutfile << \"<prof>\" << std::endl;");
		
		file.println("\tlagniappe::ProcElement * tempProc = NULL;");
		
		for (Operator o : opList) {
			if(!o.doesGenerate()) {
//				file.println("\ttempProc = mySys.createProfilerResources();");
//				file.println("\ttempProc->init();");
				file.println("\tstatic_cast<lagniappe::Operator *>(myApp.lookupOperator(\"" + o.getName() + "\"))->profile(&mySys);");
//				file.println("\tmySys.destroyProfilerResources();");
				file.println("\tstatic_cast<lagniappe::Operator *>(myApp.lookupOperator(\"" + o.getName() + "\"))->writeProfileInfo(outfile);");
			}
		}

		file.println("\toutfile << \"</prof>\" << std::endl;");
		
		file.println("\toutfile.close();");
		file.println("\texit(0);");
		file.println("}");

	}
	
	public void generateMain(String appName, String sysName)
	{
		String filename = "lc_main.cc";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}

		file.println("#include \"lc_" + appName + "_Application.h\"");
		file.println("#include <sysmodel/lc_" + sysName + "_System.h>");
		file.println();
		file.println("int");
		file.println("main(int argc, int ** argv)");
		file.println("{");
		file.println();

		file.println("\t" + appName + " myApp;");
		file.println("\t" + sysName + " mySys;");
		file.println();

		file.println("\tmyApp.setup(&mySys);");
		file.println("\tmyApp.startLoop(&mySys);");
		file.println();

		file.println("\texit(0);");
		file.println("}");

	}
	
	public void generateMain(String appName, String sysName, List<ProcGroup> pgs) {
		for (ProcGroup pg : pgs) {
			String filename = "lc_main_" + pg.getName() + ".cc";
			FileOutputStream out = null;
			PrintStream file = null;
			try {
			    out = new FileOutputStream(filename);
			    file = new PrintStream(out);
			}
			catch (FileNotFoundException e) {
			    System.err.println("Specified file " + filename + " does not exist.");
			    System.exit(1);
			}

			file.println("#include \"lc_" + appName + "_Application.h\"");
			file.println("#include <sysmodel/lc_" + sysName + "_" + pg.getName() + "_System.h>");
			file.println();
			file.println("int");
			file.println("main(int argc, int ** argv)");
			file.println("{");
			file.println();

			file.println("\t" + appName + " myApp;");
			file.println("\t" + sysName + " mySys;");
			file.println();

			file.println("\tmyApp.setup(&mySys);");
			file.println("\tmyApp.startLoop(&mySys);");
			file.println();

			file.println("\texit(0);");
			file.println("}");

			
		}
	}
	
	public void generateApplication(String name, ArrayList<Operator> opList, ArrayList<Connector> conList) {
		// Create main application builder
		String filename = "lc_" + name + "_Application.h";
		FileOutputStream out = null;
		PrintStream file = null;
		try {
		    out = new FileOutputStream(filename);
		    file = new PrintStream(out);
		}
		catch (FileNotFoundException e) {
		    System.err.println("Specified file " + filename + " does not exist.");
		    System.exit(1);
		}
		
		//guards
		file.println("#ifndef LC_" + name.toUpperCase() + "_H_");
		file.println("#define LC_" + name.toUpperCase() + "_H_");
		file.println();

		
		// std includes
		file.println("#include <map>");
		file.println("#include <utility>");
		file.println("#include <lagniappe/Application.h>");
		
		// Include operators
		for(Operator o: opList) {
			o.generateIncludes(file);
		}
		
		file.println();
		file.println("class " + name + " : public lagniappe::Application {");
		file.println();
		
		file.println();
		file.println("public:");
		file.println("\tvirtual void buildOperators(lagniappe::System * sys, bool prof = false) {");
		
		// Instantiate instances of all user defined operators
		for(Operator o: opList) {
			o.generateFrameworkCode(file);			
		}
		
		file.println("\t}");
		
		file.println();
		file.println("\tvirtual void connectGraph() {");
		file.println("\t\tstd::pair<lagniappe::CoreOp *, std::string> * tempPair;");
		for(Connector c : conList) {
			c.generateFrameworkCode(file);
		}
		file.println("\t}");
		
		// schedule
		// do a dynamic cast to prevent from scheduling system operators (request devices)
		
		file.println("\tvirtual void schedule(lagniappe::System * sys) {");
		file.println("\t\tfor(std::map<std::string, lagniappe::CoreOp *>::iterator iter = opMap.begin();");
		file.println("\t\t\titer != opMap.end(); iter++) {");
		file.println("\t\t\tlagniappe::Operator * oper = dynamic_cast<lagniappe::Operator *>(iter->second);");
		file.println("\t\t\tif(oper != NULL) {");
		file.println("\t\t\t\tlagniappe::ProcElement * tempProc = sys->getFreeProc(oper, lagniappe::INITIAL);");
		file.println("\t\t\t\tif(tempProc == NULL) tempProc = sys->roundRobin();");
		file.println("\t\t\t\toper->schedule(tempProc, true);");
		file.println("\t\t\t}");
		file.println("\t\t}");
		
		// ping the request devices that they can start receiving packets
		file.println("\t\tfor(std::map<std::string, lagniappe::CoreOp *>::iterator iter = opMap.begin();");
		file.println("\t\t\titer != opMap.end(); iter++) {");
		file.println("\t\t\tlagniappe::RequestDevice * oper = dynamic_cast<lagniappe::RequestDevice *>(iter->second);");
		file.println("\t\t\tif(oper != NULL) {");
		file.println("\t\t\t\toper->start();");
		file.println("\t\t\t}");
		file.println("\t\t}");

		file.println("\t}");
		
		file.println();
		file.println("};");
		
		//close guard
		file.println("#endif");
	}

	public void generateEntities(ArrayList<? extends Entity> entityList) {
		for(Entity e: entityList) {
			e.generateCode(this);			
		}
	}
	
	public void registerEntities(ArrayList<? extends Entity> entityList) {
		for(Entity e: entityList) {
			this.registerEntity(e);
		}
	}
	
	protected void registerEntity(Entity e) {
			entityDictionary.put(e.getName(), e);
	}

	public Entity lookupName(String name) {
		return entityDictionary.get(name);
	}
	
	public void generateSystem(String name, ArrayList<ProcessingElement> procList, 
			ArrayList<CommChannel> chanList, ArrayList<Memory> memList, ArrayList<Mutex> mtxList, 
			ArrayList<MemGroup> memGrpList, ArrayList<ProcGroup> procGrpList) {
		
		for(ProcGroup pg : procGrpList) {

			String filename = "lc_" + name + "_" + pg.getName() + "_System.h";
			FileOutputStream out = null;
			PrintStream file = null;
			try {
				out = new FileOutputStream(filename);
				file = new PrintStream(out);
			}
			catch (FileNotFoundException e) {
				System.err.println("Specified file " + filename + " does not exist.");
				System.exit(1);
			}

			// guards
			file.println("#ifndef LC_" + name.toUpperCase() + "_H_");
			file.println("#define LC_" + name.toUpperCase() + "_H_");
			file.println();


			// std includes
			file.println("#include <map>");
			file.println("#include <utility>");
			file.println("#include <lagniappe/System.h>");

			// print includes
			for(ProcessingElement p: procList) {
				if(pg.isInGroup(p.getName())) {
					p.generateIncludes(file);
				}
				else {
					p.generateGhostIncludes(file);
				}
			}
			for(Memory m : memList) {
				m.generateIncludes(file);
			}
			for(CommChannel c : chanList) {
				c.generateIncludes(file);
			}
			for(Mutex mtx: mtxList) {
				mtx.generateIncludes(file);
			}



			file.println();
			file.println("class " + name + " : public lagniappe::System {");
			file.println();
			
			file.println("public:");

			file.println("\tvirtual lagniappe::ProcElement * createProfilerResources() {");

			// use first processor in list 
			ProcessingElement p_profile = null;
			for(ProcessingElement pp : procList) {
				if(pg.isInGroup(pp.getName())) {
					p_profile = pp;
					break;
				}
			}
			file.println("\t\tprocMap[\"" + p_profile.getName() + "\"] = new " + p_profile.getName() + "(true);");
			file.println("\t\treturn procMap[\"" + p_profile.getName() + "\"];");

			file.println("\t}");

			file.println("\tvirtual void destroyProfilerResources() {");
			file.println("\t\tdelete procMap[\"" + p_profile.getName() + "\"];");
			file.println("\t}");

			file.println("\tvirtual void createResources() {");

			file.println("\t\tlagniappe::ProcElement * tempProc;");
			file.println("\t\tlagniappe::CommChannel * tempChan;");

			// create the channels
			for (CommChannel c : chanList) {
				file.println("\t\tchanMap[\"" + c.getName() + "\"] = new " + c.getName() + ";");
			}

			// create procs and assign a channel to each proc
			for (ProcessingElement p : procList) {
				if(pg.isInGroup(p.getName())) {
					file.println("\t\ttempProc = new " + p.getName() + "(false, \"" + p.getName() + "\");");
					file.println("\t\tprocMap[\"" + p.getName() + "\"] = tempProc;");
					file.println("\t\taddFreeProc(tempProc);");
					for(String s : p.getSrcChanList()) {
						file.println("\t\ttempProc->addSrcChan(chanMap[\"" + s + "\"]);");
					}
				}
				else {
					file.println("\t\ttempProc = new " + p.getName() + "Ghost(false, \"" + p.getName() + "Ghost\");");
					file.println("\t\tprocMap[\"" + p.getName() + "Ghost\"] = tempProc;");
					for(String s : p.getSrcChanList()) {
						file.println("\t\ttempProc->addSrcChan(chanMap[\"" + s + "\"]);");
					}
					
				}
			}

			// set up channel network
			for (ProcessingElement p : procList) {
				if(pg.isInGroup(p.getName())) {
					file.println("\t\ttempProc = procMap[\"" + p.getName() + "\"];");
				}
				else {
					file.println("\t\ttempProc = procMap[\"" + p.getName() + "Ghost\"];");
				}
				for(String s : p.getDstChanList()) {
					file.println("\t\ttempChan = chanMap[\"" + s + "\"];");
					CommChannel temp = (CommChannel)this.entityDictionary.get(s);
					if(pg.isInGroup(temp.getSinkProc())) {
						file.println("\t\ttempProc->addDestChan(procMap[\"" + temp.getSinkProc() + "\"], tempChan);");
					}
					else {
						file.println("\t\ttempProc->addDestChan(procMap[\"" + temp.getSinkProc() + "Ghost\"], tempChan);");
					}
				}
			}

			// create and populate the memgroups
			file.println("\t\tlagniappe::MemGroup * tempMG = NULL;");
			for (MemGroup mg : memGrpList) {
				file.println("\t\tmemGroupList.push_back(tempMG = new lagniappe::MemGroup());");
				mg.generateFrameworkCode(file, pg);
			}

			// fire up the threads
			file.println("\t\tfor(std::map<std::string, lagniappe::ProcElement *>::iterator iter = procMap.begin();");
			file.println("\t\t\titer != procMap.end(); iter++) {");
			file.println("\t\t\titer->second->init();");
			file.println("\t\t}");
			file.println("\tprocIter = procMap.begin();");
			
			for(ProcGroup pg2 : procGrpList) {
				ProcessingElement temp = pg2.getFirstProc();
				if(!pg.isInGroup(temp.getName())) {
					file.println("\tfirstProcList.push_back(procMap[\"" + temp.getName() + "Ghost\"]);");
				}
				else {
					file.println("\tmonProc = procMap[\"" + temp.getName() + "\"];");
				}
			}
			
			file.println("\t}");

			file.println("\tvirtual lagniappe::Mutex * getNewMutex() {");
			// TODO currently assuming just one mutex implementation... this is obviously not the right way to do it
			file.println("\t\treturn new " + mtxList.get(0).getName() + "();");
			file.println("\t}");
			
			file.println("\tvirtual void setMaster(lagniappe::RCon * rcon) {");
			file.println("\t\tfor(std::list<lagniappe::ProcElement *>::iterator iter = firstProcList.begin(); iter != firstProcList.end(); iter++) {");
			file.println("\t\t\tmonProc->lookupDestChan(*iter)->put(rcon);");
			file.println("\t\t}");
			file.println("\t}");

//			file.println("\tvirtual lagniappe::ProcElement * getFreeProc(lagniappe::Operator * o)");
//			file.println("\t\t{return NULL;}");

			file.println("};");

			file.println("#endif");

		}
	}
}
